﻿using System;
using System.Threading.Tasks;

using Bootstrap5.Abstractions;

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;

namespace Bootstrap5;

/// <summary>
/// Render a 'form' HTML tag with same action of <see cref="EditForm"/> component.
/// </summary>
[HtmlTag("form")]
public class Form:BootstrapComponentBase
{
    private EditContext _fixedEditContext;
    private readonly Func<Task> _handleSubmitDelegate;
    private bool _hasSetEditContextExplicitly;
    /// <summary>
    /// Initializes a new instance of the <see cref="Form"/> class.
    /// </summary>
    public Form()
    {
        _handleSubmitDelegate = Submit;
    }
	/// <summary>
	///  Specifies the top-level model object for the form. An edit context will be constructed  for this model. If using this parameter, do not also supply a value for <see cref="Form.EditContext"/>.
	/// </summary>
	[Parameter] public object Model { get; set; }
	/// <summary>
	/// Supplies the edit context explicitly. If using this parameter, do not also supply 
	/// <see cref="Model"/>, since the model value will be taken from the <see cref="EditContext.Model"/> property.
	/// </summary>
	[Parameter]
    public EditContext EditContext
    {
        get => _fixedEditContext;
        set
        {
            _fixedEditContext = value;
            _hasSetEditContextExplicitly = (value != null);
        }
    }

    /// <summary>
    /// <c>true</c> to auto create <see cref="FormLabel{TValue}"/> for all child input components, otherwise <c>false</c>.
    /// </summary>
	[Parameter] public bool AutoLabel { get; set; }= true;

	/// <summary>
	/// Gets or sets the content of the form.
	/// </summary>
	[Parameter] public RenderFragment<EditContext> ChildContent { get; set; }

    /// <summary>
    /// The callback that will be invoked when the form is submitted. 
    /// If you use this parameter, it is your responsibility to trigger any validation manually，For example, by calling
    /// <see cref="EditContext.Validate"/> method.
    /// </summary>
    [Parameter] public EventCallback<EditContext> OnSubmit { get; set; }
    /// <summary>
    ///  A callback that will be invoked when the form is submitted and the <see cref="EditContext"/> is determined to be valid.
    /// </summary>
    [Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
    /// <summary>
    /// A callback that will be invoked when the form is submitted and the <see cref="EditContext"/> is determined to be invalid.
    /// </summary>
    [Parameter] public EventCallback<EditContext> OnInvalidSubmit { get; set; }

    /// <summary>
    /// Method invoked when the component has received parameters from its parent in
    /// the render tree, and the incoming values have been assigned to properties.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// when supplying a {nameof(OnSubmit)} parameter to {nameof(Form)}, do not also supply {nameof(OnValidSubmit)} or {nameof(OnInvalidSubmit)}.
    /// </exception>
    protected override void OnParametersSet()
    {
        if (_hasSetEditContextExplicitly && Model != null)
        {
            throw new InvalidOperationException($"{nameof(Form)} required a {nameof(Model)} " +
                $"paremeter, or a {nameof(EditContext)} parameter, but not both.");
        }
        else if (!_hasSetEditContextExplicitly && Model == null)
        {
            throw new InvalidOperationException($"{nameof(Form)} requires either a {nameof(Model)} parameter, or an {nameof(EditContext)} parameter, please provide one of these.");
        }

        if (OnSubmit.HasDelegate && (OnValidSubmit.HasDelegate || OnInvalidSubmit.HasDelegate))
        {
            throw new InvalidOperationException($"when supplying a {nameof(OnSubmit)} parameter to {nameof(Form)}, do not also supply {nameof(OnValidSubmit)} or {nameof(OnInvalidSubmit)}.");
        }

        // Update _editContext if we don't have one yet, or if they are supplying a
        // potentially new EditContext, or if they are supplying a different Model
        if (Model != null && Model != _fixedEditContext?.Model)
        {
            _fixedEditContext = new EditContext(Model!);
        }
    }

    /// <summary>
    /// Renders the component to the supplied <see cref="T:Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder" />.
    /// </summary>
    /// <param name="builder">A <see cref="T:Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder" /> that will receive the render output.</param>
    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenRegion(_fixedEditContext.GetHashCode());

        builder.OpenElement(0, GetHtmlTagName());
        BuildCommonAttributes(builder);
        builder.AddAttribute(1, "onsubmit", _handleSubmitDelegate);
        this.CreateCascadingValueComponent<Form>(builder, BuildEditContextCascadingValue, isFixed: true);
        builder.CloseElement();

        builder.CloseRegion();
    }

    private void BuildEditContextCascadingValue(RenderTreeBuilder builder)
    {
        builder.OpenComponent<CascadingValue<EditContext>>(2);
        builder.AddAttribute(3, "Value", _fixedEditContext);
        builder.AddAttribute(4, "IsFixed", true);
        builder.AddAttribute(5, nameof(ChildContent), ChildContent?.Invoke(_fixedEditContext));
        builder.CloseComponent();
    }

    /// <summary>
    /// Submit this form.
    /// </summary>
    public async Task Submit()
    {
        if (OnSubmit.HasDelegate)
        {
            await OnSubmit.InvokeAsync(_fixedEditContext);
            return;
        }

        bool isValid = _fixedEditContext.Validate();
        if (isValid && OnValidSubmit.HasDelegate)
        {
            await OnValidSubmit.InvokeAsync(_fixedEditContext);
        }

        if (!isValid && OnInvalidSubmit.HasDelegate)
        {
            await OnInvalidSubmit.InvokeAsync(_fixedEditContext);
        }
    }
}
